// 版权所有2009 Go作者。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// 包tabwriter实现了一个写过滤器（tabwriter.Writer），它将输入中的选项卡列转换为正确对齐的文本。
// 
// 该软件包使用
// nickgravgaard.com/elastictabstops/index.html.
// 
// text/tabwriter包已冻结，不接受新功能。
package tabwriter

import (
	"io"
	"unicode/utf8"
)

// ---------------------------------------------------------------
// 过滤器实现

// 单元格表示以制表符或换行符结尾的文本段。
// 文本本身存储在单独的缓冲区中；单元格仅描述
// 段的字节大小、符文宽度，以及它是否是以htab 
// /（'\t'）结尾的单元格。
// 
type cell struct {
	size  int  // 以字节为单位的单元格大小
	width int  // 以符文为单位的单元格宽度
	htab  bool // 如果单元格被htab（'\t'）
}

// 终止，则为true编写器是一个过滤器，它在输入中以制表符分隔的
// 列周围插入填充，以便在输出中对齐这些列。
// 
// 写入程序将传入的字节视为UTF-8编码的文本，包含
// 由水平（'\t'）或垂直（'\v'）制表符、
// 和换行（'\n'）或换行（'\f'）字符终止的单元格；换行符和
// formfeed都充当换行符。
// 
// 相邻行中以制表符结尾的单元格构成一列。
// Writer根据需要插入填充，以使列中的所有单元格具有相同的宽度，从而有效地对齐列。它假定所有字符的宽度都相同，但必须为其指定
// tabwidth的制表符除外。列单元格必须以制表符结尾，而不是
// 制表符分隔：行末尾的非制表符结尾的尾随文本
// 形成一个单元格，但该单元格不是对齐列的一部分。例如，在这个例子中（其中|代表一个水平标签）：
// 
// aaaa | bbb | d 
// aa | b | dd 
// a | 
// aa | cccc | eee 
// 
// 终止选项卡，该列也不会是连续的）。
// 
// 编写器假设所有Unicode代码点的宽度相同；
// 在某些字体中，或者如果字符串包含组合的
// 字符，则可能不是这样。
// 
// 如果设置了DiscardeEmptyColumns，则完全由垂直（或“软”）选项卡终止的空列将被丢弃。列
// 由水平（或“硬”）选项卡终止不受
// 此标志的影响。
// 
// 如果一个编写器被配置为过滤HTML，HTML标记和实体
// 将被传递。标签和实体的宽度为
// 假设为零（标签）和一（实体），用于格式化。
// 
// 一段文本可以通过用Escape 
// 字符括起来进行转义。tabwriter通过
// /unchanged传递转义文本段。特别是，它不会解释段内的任何制表符或行
// 中断。如果设置了StripEscape标志，
// 转义字符将从输出中剥离；否则它们
// 也会通过。出于格式化的目的，转义文本的
// 宽度总是计算出来，不包括转义
// 字符。
// 
// formfeed字符的作用类似于换行符，但它也会终止当前行中的所有列（实际上是调用Flush）。Tab-
// 下一行终止的单元格开始新列。除非在HTML标记或转义文本段中找到
// 字符，否则formfeed 
// 字符将在输出中显示为换行符。
// 
// 写入程序必须在内部缓冲输入，因为一行的正确间距
// 可能取决于未来行中的单元格。客户端必须
// 调用Write时调用Flush。
// 
type Writer struct {
	// 配置
	output   io.Writer
	minwidth int
	tabwidth int
	padding  int
	padbytes [8]byte
	flags    uint

	// 当前状态
	buf     []byte   // 收集的文本不包括制表符或换行符
	pos     int      // 缓冲区位置到哪个单元格。未完成单元格的宽度已计算
	cell    cell     // 当前未完成单元格；单间牢房宽度最大为buf[pos]，不包括被忽略的部分
	endChar byte     // 转义序列的终止字符（转义为“>”、“；”用于HTML标记/实体，或0）
	lines   [][]cell // 行列表；每一行都是单元格列表
	widths  []int    // 符文列宽列表-在格式化过程中重复使用
}

// addLine添加新行。
// flushed是一个提示，指示底层写入程序是否刚刚刷新。
// 如果是这样的话，前一行不太可能是新系细胞的良好指示器。
func (b *Writer) addLine(flushed bool) {
	// 增加切片而不是追加，
	// 这给了我们一个重新使用现有[]单元的机会。
	if n := len(b.lines) + 1; n <= cap(b.lines) {
		b.lines = b.lines[:n]
		b.lines[n-1] = b.lines[n-1][:0]
	} else {
		b.lines = append(b.lines, nil)
	}

	if !flushed {
		// 前一行可能是一个很好的指标
		// 显示当前行将有多少个单元格。
		// 如果当前线路的容量小于此值，
		// 放弃该线路，新建一条。
		if n := len(b.lines); n >= 2 {
			if prev := len(b.lines[n-2]); prev > cap(b.lines[n-1]) {
				b.lines[n-1] = make([]cell, 0, prev)
			}
		}
	}
}

// 重置当前状态。
func (b *Writer) reset() {
	b.buf = b.buf[:0]
	b.pos = 0
	b.cell = cell{}
	b.endChar = 0
	b.lines = b.lines[0:0]
	b.widths = b.widths[0:0]
	b.addLine(true)
}

// 内部代表（当前状态）：
// 
// -所有书写的文本都附加到buf中；制表符和换行符被剥离
// /-在任何给定的时间，末尾有一个（可能是空的）不完整的单元格
// /（单元格在制表符或换行符后开始）
// /-单元格。size是到目前为止属于单元
// /-单元的字节数。宽度是该单元格符文中从单元格开始到
// 位置的文本宽度；如果启用了html 
// 过滤
// -已处理文本的大小和宽度保留在行列表
// 中，其中包含每行的单元格列表
// -宽度列表是一个临时列表，在
// 格式化期间使用当前宽度；它之所以保存在Writer中，是因为它被重复使用了，宽度被忽略了[---已处理---tab------<tag>..</tag>..]
// /^^^^ 
// /| | | | 
// buf不完整单元格pos的开始

// 可以使用这些标志控制格式。
const (
	// 忽略html标记，并将实体（以“&”开头，以“；”结尾）视为单个字符（宽度=1）。
	FilterHTML uint = 1 << iota

	// 将转义文本段括在括号内的转义字符去掉
	// 而不是将其与文本一起传递。
	StripEscape

	// 强制单元格内容右对齐。
	// 默认为左对齐。
	AlignRight

	// 处理空列，就好像它们不在
	// 首先处理输入一样。
	DiscardEmptyColumns

	// 始终对缩进列使用制表符（即，填充
	// 在左侧引导空单元格），而不依赖于padchar。
	TabIndent

	// 在列之间（格式化后）打印一个竖条（“|”）。
	// 丢弃的列显示为零宽度列（“| |”）。
	Debug
)

// 必须通过调用Init来初始化写入程序。第一个参数（输出）
// 指定过滤器输出。其余参数控制格式：
// 
// minwidth最小单元格宽度，包括任何填充
// tabwidth制表符宽度（相等的空格数）
// padding padding padding在计算单元格宽度之前添加到单元格中
// padchar ASCII字符用于填充
// if padchar，写入程序将假定格式化输出中“\t”的
// 宽度为tabwidth，
// 单元格与align_left 
// （要获得正确的结果，tabwidth必须与显示结果的查看器中的选项卡宽度相对应）
// 标志格式控件
// 
func (b *Writer) Init(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *Writer {
	if minwidth < 0 || tabwidth < 0 || padding < 0 {
		panic("negative minwidth, tabwidth, or padding")
	}
	b.output = output
	b.minwidth = minwidth
	b.tabwidth = tabwidth
	b.padding = padding
	for i := range b.padbytes {
		b.padbytes[i] = padchar
	}
	if padchar == '\t' {
		// 选项卡填充强制左对齐
		flags &^= AlignRight
	}
	b.flags = flags

	b.reset()

	return b
}

// 调试支持（保留代码）
func (b *Writer) dump() {
	pos := 0
	for i, line := range b.lines {
		print("(", i, ") ")
		for _, c := range line {
			print("[", string(b.buf[pos:pos+c.size]), "]")
			pos += c.size
		}
		print("\n")
	}
	print("\n")
}

// 本地错误包装器，这样我们就可以区分我们想要返回的错误
// 作为错误和真正的恐慌（我们不想返回错误）
type osError struct {
	err error
}

func (b *Writer) write0(buf []byte) {
	n, err := b.output.Write(buf)
	if n != len(buf) && err == nil {
		err = io.ErrShortWrite
	}
	if err != nil {
		panic(osError{err})
	}
}

func (b *Writer) writeN(src []byte, n int) {
	for n > len(src) {
		b.write0(src)
		n -= len(src)
	}
	b.write0(src[0:n])
}

var (
	newline = []byte{'\n'}
	tabs    = []byte("\t\t\t\t\t\t\t\t")
)

func (b *Writer) writePadding(textw, cellw int, useTabs bool) {
	if b.padbytes[0] == '\t' || useTabs {
		// 填充是用标签
		if b.tabwidth == 0 {
			return // 制表符没有宽度-不能进行任何填充
		}
		// 使cellw为b的最小倍数。tabwidth 
		cellw = (cellw + b.tabwidth - 1) / b.tabwidth * b.tabwidth
		n := cellw - textw // 填充量
		if n < 0 {
			panic("internal error")
		}
		b.writeN(tabs, (n+b.tabwidth-1)/b.tabwidth)
		return
	}

	// 填充使用非制表符
	b.writeN(b.padbytes[0:], cellw-textw)
}

var vbar = []byte{'|'}

func (b *Writer) writeLines(pos0 int, line0, line1 int) (pos int) {
	pos = pos0
	for i := line0; i < line1; i++ {
		line := b.lines[i]

		// 如果设置了TabIndent，使用制表符填充前导空单元格
		useTabs := b.flags&TabIndent != 0

		for j, c := range line {
			if j > 0 && b.flags&Debug != 0 {
				// 指示分栏
				b.write0(vbar)
			}

			if c.size == 0 {
				// 空单元格
				if j < len(b.widths) {
					b.writePadding(c.width, b.widths[j], useTabs)
				}
			} else {
				// 非空单元格
				useTabs = false
				if b.flags&AlignRight == 0 { // 左对齐
					b.write0(b.buf[pos : pos+c.size])
					pos += c.size
					if j < len(b.widths) {
						b.writePadding(c.width, b.widths[j], false)
					}
				} else { // 右对齐
					if j < len(b.widths) {
						b.writePadding(c.width, b.widths[j], false)
					}
					b.write0(b.buf[pos : pos+c.size])
					pos += c.size
				}
			}
		}

		if i+1 == len(b.lines) {
			// 最后一个缓冲行-我们没有换行，所以只要写
			// 任何未完成的缓冲数据
			b.write0(b.buf[pos : pos+b.cell.size])
			pos += b.cell.size
		} else {
			// 不是最后一行-写换行
			b.write0(newline)
		}
	}
	return
}

// 格式化第0行和第1行之间的文本（不包括第1行）；pos 
// 是对应于第0行开头的缓冲位置。
// 返回与
// 行1开头相对应的缓冲区位置，如果有错误，则返回一个错误。
// 
func (b *Writer) format(pos0 int, line0, line1 int) (pos int) {
	pos = pos0
	column := len(b.widths)
	for this := line0; this < line1; this++ {
		line := b.lines[this]

		if column >= len(line)-1 {
			continue
		}
		// 此列中存在单元格=>此行
		// 比前一行
		// （每行最后一个单元格被忽略，因为单元格是
		// 制表符终止的；每行最后一个单元格描述换行符/formfeed之前的
		// 文本，不属于
		// 列）

		// 打印未打印的行，直到块开始
		pos = b.writeLines(pos, line0, this)
		line0 = this

		// 列块开始
		width := b.minwidth // 最小列宽
		discardable := true // 如果此列中的所有单元格均为空且“软”
		for ; this < line1; this++ {
			line = b.lines[this]
			if column >= len(line)-1 {
				break
			}
			// 此列中存在单元格
			c := line[column]
			// 更新宽度
			if w := c.width + b.padding; w > width {
				width = w
			}
			// 更新可丢弃
			if c.width > 0 || c.htab {
				discardable = false
			}
		}
		// 列块结束

		// 必要时丢弃空列
		if discardable && b.flags&DiscardEmptyColumns != 0 {
			width = 0
		}

		// 格式化并打印此列右侧的所有列
		// （我们知道此列和左边所有列的宽度）
		b.widths = append(b.widths, width) // 推送宽度
		pos = b.format(pos, line0, this)
		b.widths = b.widths[0 : len(b.widths)-1] // 弹出宽度
		line0 = this
	}

	// 打印未打印的行，直到结束
	return b.writeLines(pos, line0, line1)
}

// 将文本追加到当前单元格。
func (b *Writer) append(text []byte) {
	b.buf = append(b.buf, text...)
	b.cell.size += len(text)
}

// 更新单元格宽度。
func (b *Writer) updateWidth() {
	b.cell.width += utf8.RuneCount(b.buf[b.pos:])
	b.pos = len(b.buf)
}

// 要转义文本段，请将其括在转义字符中。
// 例如，此字符串中的制表符“忽略此制表符：\xff\t\xff”
// 不终止单元格，并构成
// 的单个字符，用于格式化。
// 
// 选择值0xff是因为它不能以有效的UTF-8序列出现。
// 
const Escape = '\xff'

// 启动转义模式。
func (b *Writer) startEscape(ch byte) {
	switch ch {
	case Escape:
		b.endChar = Escape
	case '<':
		b.endChar = '>'
	case '&':
		b.endChar = ';'
	}
}

// 终止转义模式。如果转义文本是HTML标记，则其宽度
// 假定为零，以便于格式化；如果它是一个HTML实体，则
// 其宽度假定为1。在所有其他情况下，宽度是文本的
// unicode宽度。
// 
func (b *Writer) endEscape() {
	switch b.endChar {
	case Escape:
		b.updateWidth()
		if b.flags&StripEscape == 0 {
			b.cell.width -= 2 // 不计算转义字符
		}
	case '>': // 零宽度标签
	case ';':
		b.cell.width++ // 实体，计数为一符文
	}
	b.pos = len(b.buf)
	b.endChar = 0
}

// 通过将当前单元格添加到
// 当前行的单元格列表中来终止当前单元格。返回该行中的单元格数。
// 
func (b *Writer) terminateCell(htab bool) int {
	b.cell.htab = htab
	line := &b.lines[len(b.lines)-1]
	*line = append(*line, b.cell)
	b.cell = cell{}
	return len(*line)
}

func (b *Writer) handlePanic(err *error, op string) {
	if e := recover(); e != nil {
		if op == "Flush" {
			// 如果Flush陷入恐慌，我们仍然需要重置。
			b.reset()
		}
		if nerr, ok := e.(osError); ok {
			*err = nerr.err
			return
		}
		panic("tabwriter: panic during " + op)
	}
}

// 应该在最后一次调用Write之后调用Flush，以确保写入器中缓冲的任何数据都写入到输出中。任何结尾处不完整的转义序列都被视为格式完整。
func (b *Writer) Flush() error {
	return b.flush()
}

// flush是flush的内部版本，有一个我们不想公开的命名返回值。
func (b *Writer) flush() (err error) {
	defer b.handlePanic(&err, "Flush")
	b.flushNoDefers()
	return nil
}

// flushNoDefers就像flush，但没有延迟的handlePanic调用。这个
// 可以从其他已经有自己的延迟
// handlePanic调用的方法调用，比如Write，并避免额外的延迟工作。
func (b *Writer) flushNoDefers() {
	// 如果不是空的话添加当前单元格
	if b.cell.size > 0 {
		if b.endChar != 0 {
			// 内部转义-即使不完整也终止它
			b.endEscape()
		}
		b.terminateCell(false)
	}

	// 格式化缓冲区内容
	b.format(0, 0, len(b.lines))
	b.reset()
}

var hbar = []byte("---\n")

// 将buf写入写入器b。
// 返回的唯一错误是在写入底层输出流时遇到的错误。
// 
func (b *Writer) Write(buf []byte) (n int, err error) {
	defer b.handlePanic(&err, "Write")

	// 将文本拆分为单元格
	n = 0
	for i, ch := range buf {
		if b.endChar == 0 {
			// 外部转义
			switch ch {
			case '\t', '\v', '\n', '\f':
				// 单元格结束
				b.append(buf[n:i])
				b.updateWidth()
				n = i + 1 // ch消耗
				ncells := b.terminateCell(ch == '\t')
				if ch == '\n' || ch == '\f' {
					// 终止行
					b.addLine(ch == '\f')
					if ch == '\f' || ncells == 1 {
						// A'\f'始终强制刷新。否则，如果前面的
						// 行只有一个单元格，对
						// 没有影响，那么下面几行的格式（format（）会忽略每个
						// 行的最后一个单元格），这样我们就可以刷新
						// 编写器内容。
						b.flushNoDefers()
						if ch == '\f' && b.flags&Debug != 0 {
							// 指示分节
							b.write0(hbar)
						}
					}
				}

			case Escape:
				// 转义序列的开始
				b.append(buf[n:i])
				b.updateWidth()
				n = i
				if b.flags&StripEscape != 0 {
					n++ // 带转义
				}
				b.startEscape(Escape)

			case '<', '&':
				// 可能是一个html标记/实体
				if b.flags&FilterHTML != 0 {
					// 标记的开始/实体
					b.append(buf[n:i])
					b.updateWidth()
					n = i
					b.startEscape(ch)
				}
			}

		} else {
			// 内部转义
			if ch == b.endChar {
				j := i + 1
				if ch == Escape && b.flags&StripEscape != 0 {
					j = i // 带转义
				}
				b.append(buf[n:j])
				n = i + 1 // ch消耗的
				b.endEscape()
			}
		}
	}

	b.append(buf[n:])
	n = len(buf)
	return
}

// NewWriter分配并初始化一个新的tabwriter。作家
// 参数与Init函数相同。
// 
func NewWriter(output io.Writer, minwidth, tabwidth, padding int, padchar byte, flags uint) *Writer {
	return new(Writer).Init(output, minwidth, tabwidth, padding, padchar, flags)
}
